package com.tristia.resources.impl;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.http.AccessToken;

import com.google.gdata.data.books.VolumeEntry;
import com.google.gdata.data.books.VolumeFeed;
import com.google.gdata.util.ServiceException;
import com.sun.jersey.api.NotFoundException;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.tristia.dao.GenericDao;
import com.tristia.entity.Book;
import com.tristia.entity.Books;
import com.tristia.entity.Bookstores;
import com.tristia.entity.OAuthObject;
import com.tristia.entity.Review;
import com.tristia.entity.Reviews;
import com.tristia.entity.State;
import com.tristia.entity.States;
import com.tristia.entity.User;
import com.tristia.service.GoogleBooksService;

@Component
@Path("/books")
@Scope("request")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML })
public class BookResWs {
	@Resource
	GenericDao<Book, Long> bookDao;
	@Resource
	GenericDao<User, Long> userDao;
	@Resource
	GenericDao<Review, Long> reviewDao;
	@Resource
	GenericDao<State, Long> stateDao;
	@Resource
	GenericDao<OAuthObject, Long> oauthObjectDao;
	@Context 
	UriInfo ui;
	private Logger log = Logger.getLogger(this.getClass());
	private Books books = new Books();
	
	String apiKey = "06c0dfc112ecccf814429f4338c666e8";
//	String secret = "a1427d9bf4742e79";
	
	private int startIndex = 1;
	private int maxResult = 10;
	private String orderby;
	
	@GET
	public Books getBooks() {
		log.info("getBooks entered");
		books.getBooks().addAll(bookDao.getAll());
		log.info("Sending: \n\n" + books.toString() + "\n\n");
		return books;
	}
	
	@GET
	@Path("/{bookId}")
	public Book getBook(@PathParam("bookId") long bookId) {
		log.info("getBook entered");
		log.info("Hit getBook");
		Book book = bookDao.get(bookId);
		log.info("Sending: \n\n" + book.toString() + "\n\n");
		return book;
	}
	
	@GET
	@Path("/isbn/{isbn}")
	public Book getBookByISBN(@PathParam("isbn") long isbn) throws IOException, ServiceException {
		log.info("getBookByISBN entered");
		log.info("Hit getBookByISBN");
		
        HashMap<String, Object> queryParams = new HashMap<String, Object>();
        queryParams.put("isbn", isbn);
        List<Book> books = bookDao.findByNamedQuery("Book.findBookByISBN", queryParams);
        Book book = new Book();
        if (books.isEmpty()){
        	log.info("Book with the ISBN not found from Tristia Database");
        	log.info("Search from Google Books and put data into Tristia Database");
            GoogleBooksService googleBooksService = new GoogleBooksService("gdataSample-Books-1");
            VolumeFeed volumeFeed = googleBooksService.searchVolumesByISBN(isbn);
            VolumeEntry volumeEntry = new VolumeEntry();
    		if(volumeFeed.getEntries().isEmpty()){
    			log.info("Book with the ISBN not found from Google Books Database");
    		} else {
    			volumeEntry = volumeFeed.getEntries().get(0);
                log.info("Entities Title: "+volumeEntry.getTitles().toString());
                
                String title;
                String author;
                String date;
                String publisher;
                String subject;
                String intro;
                Float rating;
                
                if (!volumeEntry.getTitle().isEmpty()) {
                	title = volumeEntry.getTitle().getPlainText();
                	book.setTitle(title);
                }
                if (!volumeEntry.getCreators().isEmpty()) {
                	author = volumeEntry.getCreators().get(0).getValue();
                	book.setAuthor(author);
                }
                if (!volumeEntry.getDates().isEmpty()) {
                	date = volumeEntry.getDates().get(0).getValue();
                	book.setDate(date);
                }
                if (!volumeEntry.getPublishers().isEmpty()) {
                	publisher = volumeEntry.getPublishers().get(0).getValue();
                	book.setPublisher(publisher);
                }
                if (!volumeEntry.getSubjects().isEmpty()) {
                	subject = volumeEntry.getSubjects().get(0).getValue();
                	book.setSubject(subject);
                }
                if (!volumeEntry.getDescriptions().isEmpty()) {
                	intro = volumeEntry.getDescriptions().get(0).getValue();
                	book.setIntro(intro);
                }
                if (volumeEntry.getRating()!=null){
                	rating = volumeEntry.getRating().getAverage();
                	book.setAvgRating(rating);
                }
        		
        		book.setIsbn(isbn);
        		
        		bookDao.save(book);
        		books = bookDao.findByNamedQuery("Book.findBookByISBN", queryParams);
        		if (books.isEmpty()) {
        			log.info("WARNING: Book with the ISBN not found from Tristia database");
        		} else {
        			log.info("get book");
        			book = books.get(0);
        		}
    		}
        } else {
        	log.info("get book");
        	book = books.get(0);
        } 
        
        log.info("========================================");
		log.info("Sending: \n\n" + book.toString() + "\n\n");
		return book;
	}
	
	@GET
	@Path("/isbn/{isbn}/reviews")
	public Reviews getReviewsByISBN(@PathParam("isbn") long isbn) throws IOException, ServiceException, JSONException {
		log.info("getReviewsByISBN entered");
		Book book = new Book();
		book = getBookByISBN(isbn);

		Reviews reviews = new Reviews();
		log.info("Reviews num: " + book.getReviews().size());
		if (!book.getReviews().isEmpty()) {
			//			reviews.setReviews(book.getReviews());
			reviews.getReviews().addAll(book.getReviews());
			//			Set reviewsSet = book.getReviews();
			//			List reviewsList = new ArrayList(reviewsSet);
			//			Collections.sort(reviewsList);
			//			reviews.setReviews(reviewsList);
			Iterator<Review> iter = reviews.getReviews().iterator();
			Review review = new Review();
			
			InetAddress addr = InetAddress.getLocalHost();
			String addrStr = addr.getHostAddress();
			
			while(iter.hasNext()) {
				review = iter.next();
				review.setUsername(userDao.get(review.getUser_id()).getUsername());
				
				review.setProfileImageURL("http://"+addrStr+":9999/tristia/restws/users/"+review.getUser_id()+"/profile_image");
//				review.setProfileImageURL("http://evilkiller.oicp.net:9999/tristia/restws/users/"+review.getUser_id()+"/profile_image");
			}
		} else {
			log.info("Reviews with the ISBN not found from Tristia Database");
			log.info("Search from Douban Books and put data into Tristia Database");
			Review review = new Review();
			Client client = Client.create();
			WebResource webResource = client.resource("http://api.douban.com/book/subject/isbn/"+isbn+"/reviews?alt=json");
			
			if (webResource.head().getClientResponseStatus()==ClientResponse.Status.FOUND) {
				String tmp = webResource.get(String.class);
				JSONObject root = new JSONObject(tmp);
				JSONObject jo = null;
				JSONArray jsonArray = null;
				jsonArray = root.getJSONArray("entry");

				for (int i = 0; i < jsonArray.length(); i++) {
					jo = jsonArray.getJSONObject(i);
					String title;
					String content;
					if (jo.getJSONObject("title")!=null) {
						log.info("Reviews title: "+jo.getJSONObject("title").getString("$t"));
						title = jo.getJSONObject("title").getString("$t");
						review.setTitle(title);
					}
					if (jo.get("summary")!=null) {
						log.info("Reviews summary: "+jo.getJSONObject("summary").getString("$t"));
						content = jo.getJSONObject("summary").getString("$t");
						review.setReviewText(content);
					}
					review.setBook_id(book.getId());

					reviewDao.save(review);
				}
				HashMap<String, Object> queryParams = new HashMap<String, Object>();
				queryParams.put("book_id", book.getId());
				List<Review> reviewList = reviewDao.findByNamedQuery("Review.findReviewsByBookId", queryParams);
				reviews.getReviews().addAll(reviewList);

				if (reviews.getReviews().isEmpty()) {
					log.info("WARNING: Reviews with the ISBN not found from Tristia database");
				}

			} else if (webResource.head().getClientResponseStatus()==ClientResponse.Status.FORBIDDEN) {
				log.info("Douban access is forbidden");
			} else {
				log.info("Reviews with the ISBN not found from Douban Books Database");
			}
		}

		log.info("Sending: \n\n" + reviews.toString() + "\n\n");
		return reviews;
	}
	
	@GET
	@Path("/isbn/{isbn}/reviews/{reviewId}")
	public Review getReview(@PathParam("reviewId") long reviewId) {
		log.info("getReview entered");
		Review review = reviewDao.get(reviewId);
		if (review == null)throw new NotFoundException("Review not found");
		log.info("Sending: \n\n" + review.toString() + "\n\n");
		return review;
	}
	
	@POST
	@Path("/isbn/{isbn}/reviews")
	public Response addReviewsByISBN(@PathParam("isbn") long isbn, 
			@QueryParam("title") String title,
			@QueryParam("text") String text,
			@QueryParam("username") String username,
			@QueryParam("rating") Float rating,
			@QueryParam("book_id") Long book_id,
			@QueryParam("user_id") Long user_id) throws IOException, ServiceException {
		log.info("addReviewsByISBN entered");
		log.info("title: " + title);
		log.info("text : " + text);
		log.info("username : " + username);
		log.info("rating : " + rating);
		log.info("user_id : " + user_id);
		
		Response r;
		
		if (user_id != null) {
			Book book = new Book();
			book = getBookByISBN(isbn);
			
			Review review = new Review();
			review.setTitle(title);
			review.setReviewText(text);
			review.setUsername(username);
			review.setDatetime(Calendar.getInstance().getTime());
			review.setRating(rating);
			review.setBook_id(book.getId());
			review.setUser_id(user_id);
			reviewDao.save(review);
			log.info("Updated successfully");
			
			OAuthObject oauthObject = oauthObjectDao.get(1L);
			AccessToken accessToken = new AccessToken(oauthObject.getToken(), oauthObject.getTokenSecret());
			Twitter twitter = new TwitterFactory().getOAuthAuthorizedInstance(oauthObject.getConsumerKey(), oauthObject.getConsumerSecret(), accessToken);

			try {
				Status status = twitter.updateStatus(text);
				log.info("Successfully updated the status to [" + status.getText() + "].");
			} catch (TwitterException e) {
				// TODO Auto-generated catch block
				log.info("Twitter status do not update successfully");
				e.printStackTrace();
			}
			
			r = Response.ok(review).build();
		} else {
			log.info("Cannot post a new review without user_id");
			r = Response.noContent().build();
		}
		return r;
	}
	
	@PUT
	@Path("/isbn/{isbn}/reviews")
	public Response updateReviewsByISBN(@PathParam("isbn") long isbn, 
			@QueryParam("reviewId") String reviewId,
			@QueryParam("title") String title,
			@QueryParam("text") String text,
			@QueryParam("username") String username,
			@QueryParam("rating") Float rating,
			@QueryParam("book_id") Long book_id,
			@QueryParam("user_id") Long user_id) throws IOException, ServiceException {
		log.info("updateReview entered");
		log.info("reviewId : " + reviewId);
		log.info("title: " + title);
		log.info("text: " + text);
		
		Response r;
//		URI uri =  ui.getAbsolutePath();
		if (user_id != null) {
			if (StringUtils.hasText(reviewId)) {
				log.info("It has an ID");
				long idd = Long.parseLong(reviewId);
				boolean reviewExists = reviewDao.exists(idd);
				if (reviewExists == true) {
					log.info("We found the review with that id");
					Review review = reviewDao.get(idd);
					review.setTitle(title);
					review.setReviewText(text);
					review.setUsername(username);
					review.setDatetime(Calendar.getInstance().getTime());
					review.setRating(rating);
					reviewDao.save(review);
					log.info("Updated successfully");
					//				r = Response.created(uri).build();
					r = Response.ok(review).build();
				} else {
					log.info("We do not found the review with that id");
					r = Response.noContent().build();
				}
			} else {
				log.info("It doesn't have an ID");
				log.info("Put the new review");
				Book book = new Book();
				book = getBookByISBN(isbn);

				Review review = new Review();
				review.setTitle(title);
				review.setReviewText(text);
				review.setUsername(username);
				review.setDatetime(Calendar.getInstance().getTime());
				review.setRating(rating);
				review.setBook_id(book.getId());
				review.setUser_id(user_id);
				reviewDao.save(review);
				log.info("Updated successfully");
				
				OAuthObject oauthObject = oauthObjectDao.get(1L);
				AccessToken accessToken = new AccessToken(oauthObject.getToken(), oauthObject.getTokenSecret());
				Twitter twitter = new TwitterFactory().getOAuthAuthorizedInstance(oauthObject.getConsumerKey(), oauthObject.getConsumerSecret(), accessToken);

				try {
					Status status = twitter.updateStatus(text);
					log.info("Successfully updated the status to [" + status.getText() + "].");
				} catch (TwitterException e) {
					// TODO Auto-generated catch block
					log.info("Twitter status do not update successfully");
					e.printStackTrace();
				}
				
				r = Response.ok(review).build();
			}
		} else {
			log.info("Cannot put a new review without user_id");
			r = Response.noContent().build();
		}
		return r;
	}
	
	@DELETE
	@Path("/isbn/{isbn}/reviews/{reviewId}")
	public void deleteReviewByISBN(@PathParam("reviewId") String reviewId) {
		if(reviewId!=null) {
			deleteReviewById(reviewId);
		}
		
		MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
		List<String> ids = queryParams.get("reviewId");
		if(ids == null) {
			log.info("\n\nThe ids is null");
		} else {
			for (String currentid : ids) {
				deleteReviewById(currentid);
			}
		}
	}

	private void deleteReviewById(String id) {
		log.info("Delete Id: " + id);
		Review review = reviewDao.get(Long.parseLong(id));
		if (review == null) {
			log.info("Null was returned for ID: " + id);
		} else {
			reviewDao.remove(Long.parseLong(id));
			log.info("Deleted the review with ID:" + id);
		}
	}
	
	@GET
	@Path("/isbn/{isbn}/states")
	public States getStatesByISBN(@PathParam("isbn") long isbn) throws IOException, ServiceException, JSONException {
		log.info("getStatesByISBN entered");
		States states = new States();
		
		Book book = new Book();
		book = getBookByISBN(isbn);
		
		HashMap<String, Object> queryParams = new HashMap<String, Object>();
        queryParams.put("progress", "NONE");
        queryParams.put("book_id", book.getId());
        List<State> statesList = stateDao.findByNamedQuery("State.findValidStateByBookID", queryParams);
        log.info("States num: " + statesList.size());
        
		if (!statesList.isEmpty()) {
			states.getStates().addAll(statesList);
			Iterator<State> iter = states.getStates().iterator();
			State state = new State();
			while(iter.hasNext()) {
				state = iter.next();
				state.setUsername(userDao.get(state.getUser_id()).getUsername());
			}
		} else {
			log.info("States with the ISBN ID not found from Tristia Database");
		}
		
/*		log.info("States num: " + book.getStates().size());
		if (!book.getStates().isEmpty()) {
			states.getStates().addAll(book.getStates());
			Iterator<State> iter = states.getStates().iterator();
			State state = new State();
			while(iter.hasNext()) {
				state = iter.next();
				state.setUsername(userDao.get(state.getUser_id()).getUsername());
			}
		} else {
			log.info("States with the ISBN not found from Tristia Database");
		}*/

		log.info("Sending: \n\n" + states.toString() + "\n\n");
		return states;
	}
	
	@GET
	@Path("/isbn/{isbn}/states/{stateId}")
	public State getState(@PathParam("stateId") long stateId) {
		log.info("getState entered");
		State state = stateDao.get(stateId);
		if (state == null)throw new NotFoundException("State not found");
		log.info("Sending: \n\n" + state.toString() + "\n\n");
		return state;
	}
	
	@PUT
	@Path("/isbn/{isbn}/states")
	public Response updateStatesByISBN(@PathParam("isbn") long isbn, 
			@QueryParam("stateId") String stateId,
			@QueryParam("username") String username,
			@QueryParam("favorite") Boolean favorite,
			@QueryParam("rating") Float rating,
			@QueryParam("user_id") Long user_id) throws IOException, ServiceException {
		log.info("updateState entered");
		log.info("stateId : " + stateId);
		
		Response r;
//		URI uri =  ui.getAbsolutePath();
		if (user_id != null) {
			if (StringUtils.hasText(stateId)) {
				log.info("It has an ID");
				long idd = Long.parseLong(stateId);
				boolean stateExists = stateDao.exists(idd);
				if (stateExists == true) {
					log.info("We found the state with that id");
					State state = stateDao.get(idd);
					state.setUsername(username);
					state.setDatetime(Calendar.getInstance().getTime());
					state.setFavorite(favorite);
					state.setRating(rating);
					stateDao.save(state);
					log.info("Updated successfully");
					//				r = Response.created(uri).build();
					r = Response.ok(state).build();
				} else {
					log.info("We do not found the state with that id");
					r = Response.noContent().build();
				}
			} else {
				log.info("It doesn't have an ID");
				log.info("Put the new state");
				Book book = new Book();
				book = getBookByISBN(isbn);
				
				State state = new State();
				state.setUsername(username);
				state.setDatetime(Calendar.getInstance().getTime());
				state.setFavorite(favorite);
				state.setRating(rating);
				state.setBook_id(book.getId());
				state.setUser_id(user_id);
				stateDao.save(state);
				log.info("Updated successfully");
				r = Response.ok(state).build();
			}
		} else {
			log.info("Cannot put a new state without user_id");
			r = Response.noContent().build();
		}
		return r;
	}
	
	@GET
	@Path("/isbn/{isbn}/bookstores")
	public Response getBookstoresByISBN(@PathParam("isbn") long isbn,
			@PathParam("lat") Double lat,
			@PathParam("lon") Double lon) throws IOException, ServiceException, JSONException {
		log.info("getBookstoresByISBN entered");
		
		Response r;
		
		Bookstores bookstores = new Bookstores();
		
		Book book = new Book();
		book = getBookByISBN(isbn);
		
/*		HashMap<String, Object> queryParams = new HashMap<String, Object>();
        queryParams.put("progress", "NONE");
        queryParams.put("book_id", book.getId());
        List<State> statesList = stateDao.findByNamedQuery("State.findValidStateByBookID", queryParams);
        log.info("States num: " + statesList.size());
        
		if (!statesList.isEmpty()) {
			states.getStates().addAll(statesList);
			Iterator<State> iter = states.getStates().iterator();
			State state = new State();
			while(iter.hasNext()) {
				state = iter.next();
				state.setUsername(userDao.get(state.getUser_id()).getUsername());
			}
		} else {
			log.info("States with the ISBN ID not found from Tristia Database");
		}

		log.info("Sending: \n\n" + states.toString() + "\n\n");*/
		r = Response.ok().build();
		return r;
	}
	
	@PUT
	public Response updateBook(@QueryParam("bookId") String bookId,
			@QueryParam("title") String title,
			@QueryParam("author") String author) {
		log.info("updateBook entered");
		log.info("title: " + title);
		log.info("author : " + author);
		log.info("bookId : " + bookId);
		
		Response r;
		
		if (StringUtils.hasText(bookId)) {
			log.info("It has an ID");
			long idd = Long.parseLong(bookId);
			boolean bookExists = bookDao.exists(idd);
			if (bookExists == true) {
				log.info("We found the book with that ID");
				Book book = bookDao.get(idd);
				book.setTitle(title);
				book.setAuthor(author);
				bookDao.save(book);
				r = Response.ok(book).build();
			} else {
				log.info("We do not found the book with that ID");
				r = Response.noContent().build();
			}
		} else {
			log.info("It has not an ID");
			log.info("Put the new book");
			Book book = new Book();
			book.setTitle(title);
			book.setAuthor(author);
			bookDao.save(book);
			log.info("Updated successfully");
			r = Response.ok(book).build();
		}
		return r;
	}
	
	@POST
	public Response addBook(@QueryParam("title") String title,
			@QueryParam("author") String author) {
		log.info("addBook entered");
		log.info("title: " + title);
		log.info("author : " + author);

		Response r;
		Book book = new Book();
		book.setTitle(title);
		book.setAuthor(author);
		bookDao.save(book);
		log.info("Updated successfully");
		r = Response.ok(book).build();
		return r;
	}
	
	@DELETE
	@Path("/{bookId}")
	public void deleteBook(@PathParam("bookId") String bookId) {
		if(bookId!=null) {
			deleteBookById(bookId);
		}
		
		MultivaluedMap<String, String> queryParams = ui.getQueryParameters();
		List<String> ids = queryParams.get("bookId");
		if(ids == null) {
			log.info("\n\nThe ids is null");
		} else {
			for (String currentid : ids) {
				deleteBookById(currentid);
			}
		}
	}

	private void deleteBookById(String bookId) {
		log.info("Delete Id: " + bookId);
		Book book = bookDao.get(Long.parseLong(bookId));
		if (book == null) {
			log.info("Null was returned for ID: " + bookId);
		} else {
			bookDao.remove(Long.parseLong(bookId));
			log.info("Deleted the Book with ID:" + bookId);
		}
	}
	
	@GET
	@Path("/{bookId}/reviews")
	public Reviews getReviews(@PathParam("bookId") long bookId) {
		log.info("getReviews entered");
		Reviews reviews = new Reviews();
		log.info("Reviews num: " + bookDao.get(bookId).getReviews().size());
//		Set reviewsSet = bookDao.get(bookId).getReviews();
//		List reviewsList = new ArrayList(reviewsSet);
//		reviews.setReviews(reviewsList);
		reviews.getReviews().addAll(bookDao.get(bookId).getReviews());
//		reviews.setReviews(bookDao.get(bookId).getReviews());
		log.info("Sending: \n\n" + reviews.toString() + "\n\n");
		return reviews;
	}
	
	@GET
	@Path("/{bookId}/reviews/{reviewId}")
	public Review getReview(@PathParam("bookId") long bookId, @PathParam("reviewId") int reviewId) {
		log.info("getReview entered");
		Set<Review> reviews = bookDao.get(bookId).getReviews();
		if (reviewId > reviews.size())
            throw new NotFoundException("Review, " + reviewId + ", of Book, " + bookDao.get(bookId).getTitle() + ", is not found");
		List<Review> reviewsList = new ArrayList<Review>(reviews);
		Review review = reviewsList.get(reviewId-1);
		if (review == null)throw new NotFoundException("Review not found");
		log.info("Sending: \n\n" + review.toString() + "\n\n");
		return review;
	}
	
	/* get states */
	
	@GET
	@Path("/{bookId}/states")
	public States getStates(@PathParam("bookId") long bookId) {
		log.info("getStates entered");
		States states = new States();
		log.info("States num: " + bookDao.get(bookId).getStates().size());
		states.setStates(bookDao.get(bookId).getStates());
		log.info("Sending: \n\n" + states.toString() + "\n\n");
		return states;
	}
	
}
